package service

import (
	"context"
	"fmt"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/registry"
	"github.com/docker/docker/client"
	"github.com/goccy/go-json"
	"helper-api/app/constant"
	"helper-api/app/models/entity"
	"helper-api/app/models/request"
	"helper-api/app/models/response"
	"helper-api/app/models/vo"
	"helper-api/core"
	"helper-api/utils"
	"io"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
)

var Docker = NewDockerService()

type DockerService struct {
	client      *client.Client
	createTask  func(types int, content string, userId int64) (*entity.Execute, error)                // 创建任务记录
	existTask   func(types int) (exist bool, err error)                                               // 判断是否存在任务
	saveTask    func(id int64, data entity.Execute)                                                   // 更新任务
	executeTask func(imageName string, execute *entity.Execute, updateId int64, handler func() error) // 执行任务
}

func NewDockerService() *DockerService {
	service := new(DockerService)
	service.createTask = func(types int, content string, userId int64) (*entity.Execute, error) {
		execute := entity.Execute{
			EventType:  types,
			Content:    content,
			Status:     -1,
			CreateId:   userId,
			CreateTime: time.Now(),
			CanView:    1,
		}
		if err := core.DB.Model(&entity.Execute{}).Create(&execute).Error; err != nil {
			core.Log.Error("保存镜像拉取任务失败 => %s", err.Error())
			return nil, err
		}
		return &execute, nil
	}
	service.existTask = func(types int) (bool, error) {
		var (
			num int64
			err error
		)
		if err = core.DB.
			Model(&entity.Execute{}).
			Where("can_view = 1 and event_type = ? and status = 1", types).
			Count(&num).
			Error; err != nil || num > 0 {
			core.Log.Error("获取任务失败 => %s", err.Error())
			return true, err
		}
		return false, nil
	}
	service.saveTask = func(id int64, data entity.Execute) {
		if err := core.DB.Model(&entity.Execute{}).Where("id = ?", id).Updates(data).Error; err != nil {
			core.Log.Error("保存任务记录失败 => %s", err.Error())
		}
	}
	service.executeTask = func(imageName string, execute *entity.Execute, updateId int64, handler func() error) {
		// 将任务状态设置为正在运行
		service.saveTask(execute.Id, entity.Execute{UpdateId: updateId, Status: 1})
		// 执行任务
		if err := handler(); err != nil {
			// 读取拉取信息失败，记录错误信息
			service.saveTask(execute.Id, entity.Execute{UpdateId: updateId, Status: 0, ErrorMsg: err.Error(), Content: "执行失败"})
			return
		}
		// 拉取成功，任务记录
		service.saveTask(execute.Id, entity.Execute{UpdateId: updateId, Status: 2, Content: "执行成功"})
	}
	return service
}

// link 获取链接
func (d *DockerService) link() (*client.Client, error) {
	if d.client == nil {
		link := fmt.Sprintf("tcp://%s:2375", core.MockConfig.Docker.Address)
		cli, err := client.NewClientWithOpts(client.WithHost(link))
		if err != nil {
			return nil, err
		}
		d.client = cli
	}
	return d.client, nil
}

/** ===================================== docker container 相关方法 ============================================================= */

// ContainerList 容器列表
// Link https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerList
func (d *DockerService) ContainerList(param *request.DCListRequest) (*response.PageData, *response.BusinessError) {
	var (
		total   int
		start   int
		end     int
		cli     *client.Client
		records = make([]response.DcListResponse, 0)
		all     []types.Container
		list    []types.Container
		options types.ContainerListOptions
		err     error
	)
	options = param.BuildOptions()
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerConnectionError)
	}
	if all, err = cli.ContainerList(context.Background(), options); err != nil {
		core.Log.Error("获取容器列表错误 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerContainerListError)
	}
	// 判断是否需要更具名字筛选
	if !utils.IsEmptyString(param.Name) {
		list = make([]types.Container, 0)
		for i := 0; i < len(all); i++ {
			// 去掉前面的/
			if strings.Contains(all[i].Names[0][1:], param.Name) {
				list = append(list, all[i])
			}
		}
		total = len(list)
		if total <= 0 {
			return response.NewPageData(param.Page, param.Size, int64(total), records), nil
		}
		all = list
	}
	// 排序
	switch param.OrderField {
	case "state":
		sort.Slice(list, func(i, j int) bool {
			if param.Order == constant.OrderAsc {
				return list[i].State < list[j].State
			}
			return list[i].State > list[j].State
		})
	case "name":
		sort.Slice(list, func(i, j int) bool {
			if param.Order == constant.OrderAsc {
				return list[i].Names[0][1:] < list[j].Names[0][1:]
			}
			return list[i].Names[0][1:] > list[j].Names[0][1:]
		})
	default:
		sort.Slice(list, func(i, j int) bool {
			if param.Order == constant.OrderAsc {
				return list[i].Created < list[j].Created
			}
			return list[i].Created > list[j].Created
		})
	}
	// 截取数组
	total, start, end = len(all), (param.Page-1)*param.Size, param.Page*param.Size
	if start > total {
		// 当前页没有数据，直接可以返回
		return response.NewPageData(param.Page, param.Size, int64(total), records), nil
	}
	if end >= total {
		end = total
	}
	for _, item := range all[start:end] {
		createTime := time.Unix(item.Created, 0)
		posts := make([]string, 0)
		for _, port := range item.Ports {
			posts = append(posts, fmt.Sprintf("%d:%d/%s", port.PrivatePort, port.PublicPort, port.Type))
		}
		records = append(records, response.DcListResponse{
			Id:         item.ID,
			Name:       item.Names[0][1:],
			ImageId:    item.ImageID[6:],
			Ports:      posts,
			ImageName:  item.Image,
			CreateTime: createTime,
			State:      item.State,
			Status:     item.Status,
		})
	}
	// 重新映射新的返回对象
	return response.NewPageData(param.Page, param.Size, int64(total), records), nil
}

// ContainerOperate 容器操作
func (d *DockerService) ContainerOperate(query *request.DCOperateRequest) *response.BusinessError {
	var (
		cli *client.Client
		err error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return response.NewBusinessError(response.DockerConnectionError)
	}
	switch query.Way {
	case constant.Start:
		core.Log.Error("启动Docker容器 => %s", query.Id)
		if err = cli.ContainerStart(context.Background(), query.Id, types.ContainerStartOptions{}); err != nil {
			core.Log.Error("启动Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStartError)
		}
	case constant.Stop:
		core.Log.Error("停止Docker容器 => %s", query.Id)
		if err = cli.ContainerStop(context.Background(), query.Id, container.StopOptions{}); err != nil {
			core.Log.Error("停止Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	case constant.Restart:
		core.Log.Error("重启Docker容器 => %s", query.Id)
		if err = cli.ContainerRestart(context.Background(), query.Id, container.StopOptions{}); err != nil {
			core.Log.Error("重启Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	case constant.Kill:
		core.Log.Error("杀死Docker容器 => %s", query.Id)
		if err = cli.ContainerKill(context.Background(), query.Id, ""); err != nil {
			core.Log.Error("杀死Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	case constant.Pause:
		core.Log.Error("暂停Docker容器 => %s", query.Id)
		if err = cli.ContainerPause(context.Background(), query.Id); err != nil {
			core.Log.Error("暂停Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	case constant.Unpause:
		core.Log.Error("恢复Docker容器 => %s", query.Id)
		if err = cli.ContainerUnpause(context.Background(), query.Id); err != nil {
			core.Log.Error("恢复Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	case constant.Rename:
		core.Log.Error("重命名Docker容器 => %s", query.Id)
		if err = cli.ContainerRename(context.Background(), query.Id, query.Name); err != nil {
			core.Log.Error("重命名Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	case constant.Remove:
		core.Log.Error("杀死Docker容器 => %s", query.Id)
		if err = cli.ContainerRemove(context.Background(), query.Id, types.ContainerRemoveOptions{
			RemoveVolumes: query.RemoveVolumes,
			RemoveLinks:   query.RemoveLinks,
			Force:         query.Force,
		}); err != nil {
			core.Log.Error("杀死Docker容器失败 => %s", err.Error())
			return response.NewBusinessError(response.DockerConnectionStopError)
		}
	}
	return nil
}

// ContainerInspect 获取容器详情
func (d *DockerService) ContainerInspect(id string) (string, *response.BusinessError) {
	return "", nil
}

// ContainerLogs 获取容器日志
func (d *DockerService) ContainerLogs(param request.DCLogQueryRequest) (string, *response.BusinessError) {
	return "", nil
}

/** ===================================== docker images 相关方法 ============================================================= */

// ImageList 镜像分页
// https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageList
func (d *DockerService) ImageList(param *request.DIListRequest) (*response.PageData, *response.BusinessError) {
	var (
		list        []types.ImageSummary
		records     = make([]response.DIListResponse, 0)
		filterSlice = func(list []types.ImageSummary, predicate func(target types.ImageSummary) bool) []types.ImageSummary {
			result := make([]types.ImageSummary, 0)
			for _, item := range list {
				if predicate(item) {
					result = append(result, item)
				}
			}
			return result
		}
		cli   *client.Client
		total int
		start int
		end   int
		err   error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerConnectionError)
	}
	if list, err = cli.ImageList(context.Background(), types.ImageListOptions{All: param.IsAll}); err != nil {
		core.Log.Error("获取容器镜像列表错误 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerImageListError)
	}
	// 过滤数据
	if !utils.IsEmptyString(param.Name) {
		list = filterSlice(list, func(target types.ImageSummary) bool {
			for _, item := range target.RepoTags {
				if strings.Contains(item, param.Name) {
					return true
				}
			}
			return false
		})
	}
	// 排序
	switch param.OrderField {
	case "size":
		sort.Slice(list, func(i, j int) bool {
			if param.Order == constant.OrderAsc {
				return list[i].Size < list[j].Size
			}
			return list[i].Size > list[j].Size
		})
	case "name":
		sort.Slice(list, func(i, j int) bool {
			if param.Order == constant.OrderAsc {
				return list[i].RepoTags[0][1:] < list[j].RepoTags[0][1:]
			}
			return list[i].RepoTags[0][1:] > list[j].RepoTags[0][1:]
		})
	default:
		sort.Slice(list, func(i, j int) bool {
			if param.Order == constant.OrderAsc {
				return list[i].Created < list[j].Created
			}
			return list[i].Created > list[j].Created
		})
	}
	// 物理分页
	total, start, end = len(list), (param.Page-1)*param.Size, param.Page*param.Size
	if start > total {
		// 当前页没有数据，直接可以返回
		return response.NewPageData(param.Page, param.Size, int64(total), records), nil
	}
	if end >= total {
		end = total
	}
	for _, item := range list[start:end] {
		createTime := time.Unix(item.Created, 0)
		records = append(records, response.DIListResponse{
			Id:         item.ID[7:],
			Size:       item.Size,
			CreateTime: createTime,
			Tags:       item.RepoTags,
			Container:  item.Containers,
		})
	}
	// 重新映射新的返回对象
	return response.NewPageData(param.Page, param.Size, int64(total), records), nil
}

// ImageSearch 镜像搜索
func (d *DockerService) ImageSearch(param *request.DISearchRequest) ([]response.DISearchResponse, *response.BusinessError) {
	var (
		cli     *client.Client
		options = types.ImageSearchOptions{Filters: filters.NewArgs()}
		list    = make([]response.DISearchResponse, 0)
		result  []registry.SearchResult
		err     error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerConnectionError)
	}
	if param.Limit > 0 {
		options.Limit = param.Limit
	}
	if param.IsAutomated != -1 {
		if param.IsAutomated == 0 {
			options.Filters.Add("is-automated", "false")
		}
		if param.IsAutomated == 1 {
			options.Filters.Add("is-automated", "true")
		}
	}
	if param.IsOfficial != -1 {
		if param.IsOfficial == 0 {
			options.Filters.Add("is-official", "false")
		}
		if param.IsOfficial == 1 {
			options.Filters.Add("is-official", "true")
		}
	}
	if param.Stars > 0 {
		options.Filters.Add("stars", strconv.Itoa(param.Stars))
	}
	if result, err = cli.ImageSearch(context.Background(), param.Name, options); err != nil {
		core.Log.Error("搜索Docker镜像失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerImageSearchError)
	}
	if len(result) <= 0 {
		return list, nil
	}
	for _, item := range result {
		list = append(list, response.DISearchResponse{
			Name:      item.Name,
			Official:  item.IsOfficial,
			Automated: item.IsAutomated,
			Desc:      item.Description,
			Star:      item.StarCount,
		})
	}
	// 排序
	sort.Slice(list, func(i, j int) bool {
		return list[i].Star > list[j].Star
	})
	return list, nil
}

// ImagePull 镜像拉取
// https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageCreate
func (d *DockerService) ImagePull(name string, userId int64) (*response.DIPullResultResponse, *response.BusinessError) {
	var (
		runningNum int64
		cli        *client.Client
		err        error
		execute    entity.Execute
		// 更新任务信息
		saveTask = func(id int64, userId int64, status int, content string) {
			data := entity.Execute{UpdateId: userId, Status: status}
			if status == 0 {
				data.ErrorMsg = content
			}
			if err := core.DB.Model(&entity.Execute{}).Where("id = ?", id).Updates(data).Error; err != nil {
				core.Log.Error("保存任务记录失败 => %s", err.Error())
			}
		}
		// 具体执行拉取镜像的任务
		handler = func(imageName string, execute *entity.Execute, updateId int64) {
			var (
				out io.ReadCloser
				err error
			)
			// 将任务状态设置为正在运行
			saveTask(execute.Id, updateId, 1, "")
			// 镜像拉取
			if out, err = cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{}); err != nil {
				// 拉取失败，记录错误信息
				saveTask(execute.Id, updateId, 0, err.Error())
				return
			}
			defer out.Close()
			if _, err = io.ReadAll(out); err != nil {
				// 读取拉取信息失败，记录错误信息
				saveTask(execute.Id, updateId, 0, err.Error())
				return
			}
			// 拉取成功，任务记录
			saveTask(execute.Id, updateId, 2, "拉取成功")
		}
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerConnectionError)
	}
	// 判断是否存在正在拉取的image
	if err = core.DB.
		Model(&entity.Execute{}).
		Where("can_view = 1 and event_type = 1 and status = 1").
		Count(&runningNum).
		Error; err != nil || runningNum > 0 {
		core.Log.Error("获取正在执行镜像拉取任务失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ExistRunningTaskError)
	}
	// 写入任务
	execute = entity.Execute{
		EventType:  1,
		Content:    name,
		Status:     -1,
		CreateId:   userId,
		CreateTime: time.Now(),
		CanView:    1,
	}
	if err = core.DB.Model(&entity.Execute{}).Create(&execute).Error; err != nil {
		core.Log.Error("保存镜像拉取任务失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ExistRunningTaskError)
	}
	// 启动协程
	go handler(name, &execute, userId)
	return &response.DIPullResultResponse{
		Id:     execute.Id,
		Status: execute.Status,
		Msg:    "",
	}, nil
}

// ImagePullResult 获取镜像拉取结果
func (d *DockerService) ImagePullResult(id int64) (response.DIPullResultResponse, *response.BusinessError) {
	var (
		result response.DIPullResultResponse
		data   entity.Execute
		err    error
	)
	if err = core.DB.Model(&entity.Execute{}).Where("id = ?", id).First(&data).Error; err != nil {
		core.Log.Error("获取执行任务失败 => %s", err.Error())
		return result, response.NewBusinessError(response.DataNotExist)
	}
	result = response.DIPullResultResponse{
		Id:     id,
		Status: data.Status,
		Msg:    data.ErrorMsg,
	}
	return result, nil
}

// ImageTags 获取镜像Tag
// https://hub.docker.com/v2/repositories/library/wordpress/tags/?page_size=25&page=1&name&ordering
// ordering  -last_updated=>oldest | last_updated=>newest | -name=>a-z | name=>z-a
func (d *DockerService) ImageTags(param request.DIImageTagRequest) (*response.PageData, *response.BusinessError) {
	var (
		baseUrl string
		bodyObj vo.ImageTagVo
		cli     = &http.Client{}
		req     *http.Request
		res     *http.Response
		body    []byte
		err     error
	)
	baseUrl = fmt.Sprintf("https://hub.docker.com/v2/repositories/library/%s/tags/?page_size=%d&page=%d", param.ImageName, param.Size, param.Page)
	if param.Name != "" {
		baseUrl += "&name=" + param.Name
	}
	if param.Ordering != "" {
		baseUrl += "&ordering=" + param.Ordering
	}
	if req, err = http.NewRequest("get", baseUrl, nil); err != nil {
		core.Log.Error("构建请求失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ImageTagsGetError)
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
	if res, err = cli.Do(req); err != nil {
		core.Log.Error("发送请求失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ImageTagsGetError)
	}
	defer res.Body.Close()
	if body, err = io.ReadAll(res.Body); err != nil {
		core.Log.Error("读取返回数据失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ImageTagsGetError)
	}
	core.Log.Error("获取Tag => %s", string(body))
	if err = json.Unmarshal(body, &bodyObj); err != nil {
		core.Log.Error("反序列化数据失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ImageTagsGetError)
	}
	return &response.PageData{
		Total: bodyObj.Count,
		Page:  param.Page,
		Size:  param.Size,
		Data:  bodyObj.Results,
	}, nil
}

// ImageSetTag 设置镜像Tag
func (d *DockerService) ImageSetTag(param request.DISetTagRequest) *response.BusinessError {
	var (
		cli *client.Client
		err error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return response.NewBusinessError(response.DockerConnectionError)
	}
	if err = cli.ImageTag(context.Background(), param.ImageId, param.Tag); err != nil {
		return response.NewBusinessError(response.ImageTagSetError)
	}
	return nil
}

// ImageRemove 镜像移除
func (d *DockerService) ImageRemove(imageId string, force bool) *response.BusinessError {
	var (
		cli *client.Client
		err error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return response.NewBusinessError(response.DockerConnectionError)
	}
	if _, err = cli.ImageRemove(context.Background(), imageId, types.ImageRemoveOptions{Force: force}); err != nil {
		core.Log.Error("镜像删除失败 => %s", err.Error())
		return response.NewBusinessError(response.ImageDeleteError)
	}
	return nil
}

// ImagesInfo 获取镜像详情
// https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageInspect
func (d *DockerService) ImagesInfo(imageId string) (*types.ImageInspect, *response.BusinessError) {
	var (
		inspect types.ImageInspect
		cli     *client.Client
		err     error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerConnectionError)
	}
	if inspect, _, err = cli.ImageInspectWithRaw(context.Background(), imageId); err != nil {
		core.Log.Error("获取镜像详情失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ImageDeleteError)
	}
	return &inspect, nil
}

// ImageExport 镜像导出
// https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageGet
func (d *DockerService) ImageExport(param request.DIExportRequest) (*response.DIPullResultResponse, *response.BusinessError) {
	var (
		execute *entity.Execute
		cli     *client.Client
		exist   bool
		err     error
	)
	if cli, err = d.link(); err != nil {
		core.Log.Error("初始化Docker连接失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.DockerConnectionError)
	}
	// 判断是否存在正在导出的任务
	if exist, err = d.existTask(2); exist {
		return nil, response.NewBusinessError(response.ExistRunningTaskError)
	}
	// 写入任务
	if execute, err = d.createTask(2, param.NewName, param.UserId); err != nil {
		core.Log.Error("保存镜像拉取任务失败 => %s", err.Error())
		return nil, response.NewBusinessError(response.ExistRunningTaskError)
	}
	// 启动协程
	go d.executeTask(param.NewName, execute, param.UserId, func() error {
		var (
			path = fmt.Sprintf("%s/%s.tar", param.Path, param.NewName)
			file *os.File
			out  io.ReadCloser
			err  error
		)
		if out, err = cli.ImageSave(context.Background(), []string{param.ImageId}); err != nil {
			return err
		}
		defer out.Close()
		if file, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666); err != nil {
			return err
		}
		defer file.Close()
		if _, err = io.Copy(file, out); err != nil {
			return err
		}
		return nil
	})
	return &response.DIPullResultResponse{
		Id:     execute.Id,
		Status: execute.Status,
		Msg:    "",
	}, nil
}

/** ===================================== docker volumes 相关方法 ============================================================= */
/** ===================================== docker network 相关方法 ============================================================= */
